#include "ft2build.h"
#include FT_FREETYPE_H
#include FT_STROKER_H

#include "font.h"
#include "atlas.h"

struct raster_t
{
	FT_BBox   bbox;
	uint32_t  channel;
	bitmap_t *bitmap;
};

static void rasterize(int y, int count, const FT_Span *spans, void *user)
{
	raster_t *rst = (raster_t *)user;
	bitmap_t *bmp = rst->bitmap;
	
	for (int i = 0; i < count; ++i)
	{
		const FT_Span *span = spans + i;
		
		for (int j = 0; j < span->len; ++j)
		{
			uint8_t *data = (uint8_t *)(bmp->data + (span->x - rst->bbox.xMin + j)
				+ (bmp->h - 1 - (y - rst->bbox.yMin)) * bmp->w);
			
			data[rst->channel] = span->coverage;
		}
	}
}

static void do_rasterize(FT_Library &library, FT_Outline *outline, raster_t *raster)
{
	FT_Raster_Params params;
	memset(&params, 0, sizeof(params));
	
	params.flags      = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
	params.gray_spans = rasterize;
	params.user       = raster;
	
	FT_Outline_Render(library, outline, &params);
}

font_desc_t* font_create(const char *name, uint16_t size, uint16_t owidth, uint16_t width, uint16_t height)
{
	FT_Error      error;
	FT_Library    library;
	FT_Face       face;
	FT_GlyphSlot  slot;
	FT_Bitmap    *bitmap;
	FT_Stroker    stroker;
	FT_Glyph      ftglyph;
	
	atlas_t *atlas = atlas_create(width, height);
	if (atlas == NULL)
		return NULL;
	
	if ((error = FT_Init_FreeType(&library)) != 0)
		return NULL;
	
	if ((error = FT_New_Face(library, name, 0, &face)) != 0)
		return NULL;
	
	if ((error = FT_Set_Pixel_Sizes(face, 0, size)) != 0)
		return NULL;
	
	bool kerning = FT_HAS_KERNING(face);
	
	slot = face->glyph;
	
	font_desc_t *desc = (font_desc_t *)malloc(sizeof(font_desc_t) + width * height * 2);
	memset(desc, 0, sizeof(font_desc_t) + width * height * 2);
	
	desc->width  = width;
	desc->height = height;
	
	raster_t raster;
	
	for (uint32_t i = 0; i < 0x100; ++i)
	{
		FT_UInt index = FT_Get_Char_Index(face, i);
		
		if (!index) continue;
		
		glyph_t *glyph = desc->font.glyphs + i;
		/*
		if (kerning)
		{
			for (uint32_t j = 0; j < 0x100; ++j)
			{
				uint16_t ch = j + 0x20;
				ch = ch >= 0x80 ? ch + 0x410 - 0x80 : ch;
				
				if (ch >= 0x450) break;
				
				FT_UInt kindex = FT_Get_Char_Index(face, ch);
				
				if  (!kindex) continue;
				
				FT_Vector delta;
				FT_Get_Kerning(face, index, kindex, FT_KERNING_DEFAULT, &delta);
				
				glyph->kern[j] = delta.x >> 6;
			}
		}
		*/
		if ((error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT)) != 0)
			continue;
		
		if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
			continue;
		
		if ((error = FT_Get_Glyph(slot, &ftglyph)) != 0)
			continue;
		
		FT_Glyph_Get_CBox(ftglyph, FT_GLYPH_BBOX_PIXELS, &raster.bbox);
		
		if (owidth)
		{
			FT_Stroker_New(library, &stroker);
			FT_Stroker_Set(stroker, owidth << 4, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
			FT_Glyph_StrokeBorder(&ftglyph, stroker, 0, 1);
			
			if (ftglyph->format == FT_GLYPH_FORMAT_OUTLINE)
				FT_Glyph_Get_CBox(ftglyph, FT_GLYPH_BBOX_PIXELS, &raster.bbox);
		}
		
		int32_t width  = raster.bbox.xMax - raster.bbox.xMin;
		int32_t height = raster.bbox.yMax - raster.bbox.yMin;
		
		raster.bitmap = (bitmap_t *)malloc(sizeof(bitmap_t) + width * height * 2);
		memset(raster.bitmap, 0, sizeof(bitmap_t) + width * height * 2);
		
		raster.bitmap->w = width;
		raster.bitmap->h = height;
		
		raster.channel = 0;
		do_rasterize(library, &slot->outline, &raster);
		
		if (owidth)
		{
			if (ftglyph->format == FT_GLYPH_FORMAT_OUTLINE)
			{
				FT_Outline *outline = &((FT_OutlineGlyph)ftglyph)->outline;
				
				raster.channel = 1;
				do_rasterize(library, outline, &raster);
			}
			
			FT_Stroker_Done(stroker);
		}
		
		glyph->ch   = i;
		glyph->xadv = (ftglyph->advance.x / 1000L) >> 6;
		glyph->yadv = (ftglyph->advance.y / 1000L) >> 6;
		glyph->xoff = slot->metrics.horiBearingX >> 6;
		glyph->yoff = slot->metrics.horiBearingY >> 6;
		glyph->w    = raster.bitmap->w;
		glyph->h    = raster.bitmap->h;
		
		FT_Done_Glyph(ftglyph);
		
		node_t *node = atlas_insert(atlas, raster.bitmap);
		if (node == NULL)
		{
			memset(glyph, 0, sizeof(glyph_t));
			free(raster.bitmap);
			break;
		}
		
		glyph->x = node->rect.x;
		glyph->y = node->rect.y;
	}
	
	atlas_assemble(atlas, desc->bitmap, desc->width, desc->height);
	atlas_destroy(atlas);
	return desc;
}

void font_destroy(font_desc_t *desc)
{
	// do nothing
}

void font_save(const font_desc_t *desc, const char *name)
{
	FILE *output = fopen(name, "wb");
	
	if (output != NULL)
	{
		fwrite(desc, 1, sizeof(font_desc_t) + desc->width * desc->height * 2, output);
		fclose(output);
	}
}
